/* Copyright (c) 2020 XEPIC Corporation Limited */
/*
 * Copyright (c) 2003-2015 Stephen Williams (steve@icarus.com)
 *
 *    This source code is free software; you can redistribute it
 *    and/or modify it in source code form under the terms of the GNU
 *    General Public License as published by the Free Software
 *    Foundation; either version 2 of the License, or (at your option)
 *    any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 * USA.
 */

#include "array.h"
#include "compile.h"
#include "logic.h"
#include "schedule.h"
#include "vpi_priv.h"
#include "vvp_net_sig.h"
#ifdef CHECK_WITH_VALGRIND
#include "vvp_cleanup.h"
#endif
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>

static void __compile_var_real(char* label, char* name, vvp_array_t array,
                               unsigned long array_addr) {
  vvp_net_t* net = new vvp_net_t;

  if (vpip_peek_current_scope()->is_automatic()) {
    vvp_fun_signal_real_aa* tmp = new vvp_fun_signal_real_aa;
    net->fil = tmp;
    net->fun = tmp;
  } else {
    net->fil = new vvp_wire_real;
    net->fun = new vvp_fun_signal_real_sa;
  }

  define_functor_symbol(label, net);

  vpiHandle obj = vpip_make_real_var(name, net);
  compile_vpi_symbol(label, obj);

  if (name) {
    assert(!array);
    vpip_attach_to_current_scope(obj);
    if (!vpip_peek_current_scope()->is_automatic())
      schedule_init_vector(vvp_net_ptr_t(net, 0), 0.0);
  }
  if (array) {
    assert(!name);
    array->attach_word(array_addr, obj);
  }
  free(label);
  delete[] name;
}

void compile_var_real(char* label, char* name) {
  __compile_var_real(label, name, 0, 0);
}

void compile_varw_real(char* label, vvp_array_t array, unsigned long addr,
                       int msb, int lsb) {
  assert(msb == 0 && lsb == 0);
  __compile_var_real(label, 0, array, addr);
}

void compile_var_string(char* label, char* name) {
  vvp_net_t* net = new vvp_net_t;
#ifdef HAVE_READABLE_INFO
  net->add_readable_info(label, name);
#endif
  if (vpip_peek_current_scope()->is_automatic()) {
    vvp_fun_signal_string_aa* tmp = new vvp_fun_signal_string_aa;
    net->fil = tmp;
    net->fun = tmp;
  } else {
    net->fil = 0;
    net->fun = new vvp_fun_signal_string_sa;
  }

  define_functor_symbol(label, net);

  vpiHandle obj = vpip_make_string_var(name, net);
  compile_vpi_symbol(label, obj);

  vpip_attach_to_current_scope(obj);
  free(label);
  delete[] name;
}

void compile_var_darray(char* label, char* name) {
  vvp_net_t* net = new vvp_net_t;

  if (vpip_peek_current_scope()->is_automatic()) {
    vvp_fun_signal_object_aa* tmp = new vvp_fun_signal_object_aa;
    net->fil = tmp;
    net->fun = tmp;
  } else {
    net->fil = 0;
    net->fun = new vvp_fun_signal_object_sa;
  }

  define_functor_symbol(label, net);

  vpiHandle obj = vpip_make_darray_var(name, net);
  compile_vpi_symbol(label, obj);

  vpip_attach_to_current_scope(obj);
  free(label);
  delete[] name;
}

void compile_var_queue(char* label, char* name) {
  vvp_net_t* net = new vvp_net_t;

  if (vpip_peek_current_scope()->is_automatic()) {
    vvp_fun_signal_object_aa* tmp = new vvp_fun_signal_object_aa;
    net->fil = tmp;
    net->fun = tmp;
  } else {
    net->fil = 0;
    net->fun = new vvp_fun_signal_object_sa;
  }

  define_functor_symbol(label, net);

  vpiHandle obj = vpip_make_queue_var(name, net);
  compile_vpi_symbol(label, obj);

  vpip_attach_to_current_scope(obj);
  free(label);
  delete[] name;
}

void compile_var_cobject(char* label, char* name) {
  vvp_net_t* net = new vvp_net_t;

  if (vpip_peek_current_scope()->is_automatic()) {
    vvp_fun_signal_object_aa* tmp = new vvp_fun_signal_object_aa;
    net->fil = tmp;
    net->fun = tmp;
  } else {
    net->fil = 0;
    net->fun = new vvp_fun_signal_object_sa;
  }

  define_functor_symbol(label, net);

  vpiHandle obj = vpip_make_cobject_var(name, net);
  compile_vpi_symbol(label, obj);

  vpip_attach_to_current_scope(obj);
  free(label);
  delete[] name;
}

/*
 * A variable is a special functor, so we allocate that functor and
 * write the label into the symbol table.
 */
void compile_variable(char* label, char* name, int msb, int lsb,
                      int vpi_type_code, bool signed_flag, bool local_flag) {
  unsigned wid = ((msb > lsb) ? msb - lsb : lsb - msb) + 1;

  vvp_net_t* net = new vvp_net_t;
#ifdef HAVE_READABLE_INFO
  char szInfo[DEBUG_INFO_LEN + 1] = {0};
  snprintf(szInfo, DEBUG_INFO_LEN, "%s .var %s, %d %d;", label, name, msb, lsb);
  net->add_readable_info(label, name);
  net->add_readable_info(szInfo);
#endif
  if (vpip_peek_current_scope()->is_automatic()) {
    vvp_fun_signal4_aa* tmp = new vvp_fun_signal4_aa(wid);
    net->fil = tmp;
    net->fun = tmp;
  } else if (vpi_type_code == vpiIntVar) {
    net->fil = new vvp_wire_vec4(wid, BIT4_0);
    net->fun = new vvp_fun_signal4_sa(wid);
  } else {
    net->fil = new vvp_wire_vec4(wid, BIT4_X);
    net->fun = new vvp_fun_signal4_sa(wid);
  }
  vvp_signal_value* vfil = dynamic_cast<vvp_signal_value*>(net->fil);

  define_functor_symbol(label, net);

  vpiHandle obj = 0;
  if (!local_flag) {
    /* Make the vpiHandle for the reg. */
    switch (vpi_type_code) {
      case vpiLogicVar:
        obj = vpip_make_var4(name, msb, lsb, signed_flag, net);
        break;
      case vpiIntegerVar:
        obj = vpip_make_int4(name, msb, lsb, net);
        break;
      case vpiIntVar:  // This handles all the atom2 int types
        obj = vpip_make_int2(name, msb, lsb, signed_flag, net);
        break;
      default:
        fprintf(stderr, "internal error: %s: vpi_type_code=%d\n", name,
                vpi_type_code);
        break;
    }
    assert(obj);
    compile_vpi_symbol(label, obj);
  }
  // If the signal has a name, then it goes into the current
  // scope as a signal.
  if (name) {
    if (obj) vpip_attach_to_current_scope(obj);
    if (!vpip_peek_current_scope()->is_automatic()) {
      vvp_vector4_t tmp;
      vfil->vec4_value(tmp);
      schedule_init_vector(vvp_net_ptr_t(net, 0), tmp);
    }
  }

  free(label);
  delete[] name;
}

vvp_net_t* create_constant_node(const char* val_str) {
  if (c4string_test(val_str)) {
    vvp_net_t* net = new vvp_net_t;
    net->fun = new vvp_fun_bufz;
    schedule_init_vector(vvp_net_ptr_t(net, 0), c4string_to_vector4(val_str));
    return net;
  }

  if (c8string_test(val_str)) {
    vvp_net_t* net = new vvp_net_t;
    net->fun = new vvp_fun_bufz;
    schedule_init_vector(vvp_net_ptr_t(net, 0), c8string_to_vector8(val_str));
    return net;
  }

  if (crstring_test(val_str)) {
    vvp_net_t* net = new vvp_net_t;
    net->fun = new vvp_fun_bufz;
    schedule_init_vector(vvp_net_ptr_t(net, 0), crstring_to_double(val_str));
    return net;
  }

  return 0;
}

class base_net_resolv : public resolv_list_s {
 public:
  explicit base_net_resolv(char* ref_label, vvp_array_t array,
                           __vpiScope* scope, char* my_label, char* name,
                           unsigned array_addr, bool local_flag)
      : resolv_list_s(ref_label) {
    my_label_ = my_label;
    array_ = array;
    name_ = name;
    scope_ = scope;
    array_addr_ = array_addr;
    local_flag_ = local_flag;
  }

 protected:
  char* my_label_;
  vvp_array_t array_;
  char* name_;
  __vpiScope* scope_;
  unsigned array_addr_;
  bool local_flag_;
};

class __compile_net_resolv : public base_net_resolv {
 public:
  explicit __compile_net_resolv(char* ref_label, vvp_array_t array,
                                __vpiScope* scope, char* my_label, char* name,
                                int msb, int lsb, unsigned array_addr,
                                int vpi_type_code, bool signed_flag,
                                bool local_flag)
      : base_net_resolv(ref_label, array, scope, my_label, name, array_addr,
                        local_flag) {
    msb_ = msb;
    lsb_ = lsb;
    vpi_type_code_ = vpi_type_code;
    signed_flag_ = signed_flag;
  }

  ~__compile_net_resolv() {}

  bool resolve(bool message_flag);

 private:
  int msb_, lsb_;
  int vpi_type_code_;
  bool signed_flag_;
};

/*
 * Here we handle .net records from the vvp source:
 *
 *    .net   <name>, <msb>, <lsb>, <input> ;
 *    .net/s <name>, <msb>, <lsb>, <input> ;
 *    .net8   <name>, <msb>, <lsb>, <input> ;
 *    .net8/s <name>, <msb>, <lsb>, <input> ;
 *
 * Create a VPI handle to represent it, and fill that handle in with
 * references into the net.
 */

static void do_compile_net(vvp_net_t* node, vvp_array_t array,
                           __vpiScope* scope, char* my_label, char* name,
                           int msb, int lsb, unsigned array_addr,
                           int vpi_type_code, bool signed_flag,
                           bool local_flag) {
  unsigned wid = ((msb > lsb) ? msb - lsb : lsb - msb) + 1;
  assert(node);

  vvp_wire_base* vsig = dynamic_cast<vvp_wire_base*>(node->fil);

  if (vsig == 0) {
    switch (vpi_type_code) {
      case vpiIntVar:
        vsig = new vvp_wire_vec4(wid, BIT4_0);
        break;
      case vpiLogicVar:
        vsig = new vvp_wire_vec4(wid, BIT4_Z);
        break;
      case -vpiLogicVar:
        vsig = new vvp_wire_vec8(wid);
        break;
    }
    assert(vsig);
    node->fil = vsig;
  }

  vpiHandle obj = 0;
  if (!local_flag) {
    /* Make the vpiHandle for the reg. */
    obj = vpip_make_net4(scope, name, msb, lsb, signed_flag, node);
    /* This attaches the label to the vpiHandle */
    compile_vpi_symbol(my_label, obj);
  }
#ifdef CHECK_WITH_VALGRIND
  else
    pool_local_net(node);
#endif

  // REMOVE ME! Giving the net a label is a legacy of the times
  // when the .net was a functor of its own. In the long run, we
  // must fix the code generator to not rely on the label of the
  // .net, then we will remove that label.
  define_functor_symbol(my_label, node);

  if (array)
    array->attach_word(array_addr, obj);
  else if (obj)
    vpip_attach_to_scope(scope, obj);

  free(my_label);
  delete[] name;
}

static void __compile_net(char* label, char* name, char* array_label,
                          unsigned long array_addr, int msb, int lsb,
                          int vpi_type_code, bool signed_flag, bool local_flag,
                          unsigned argc, struct symb_s* argv) {
  vvp_array_t array = array_label ? array_find(array_label) : 0;
  assert(array_label ? array != 0 : true);

  free(array_label);

  assert(argc == 1);
  vvp_net_t* node = vvp_net_lookup(argv[0].text);
#if 1
  if (node == 0) {
    /* No existing net, but the string value may be a
       constant. In that case, we will wind up generating a
       bufz node that can carry the constant value.

       NOTE: This is a hack! The code generator should be
       fixed so that this is no longer needed. */
    node = create_constant_node(argv[0].text);
  }
#endif
  if (node == 0) {
    __vpiScope* scope = vpip_peek_current_scope();
    __compile_net_resolv* res = new __compile_net_resolv(
        argv[0].text, array, scope, label, name, msb, lsb, array_addr,
        vpi_type_code, signed_flag, local_flag);
    resolv_submit(res);
    free(argv);
    return;
  }
  assert(node);
#ifdef HAVE_READABLE_INFO
  char szInfo[DEBUG_INFO_LEN + 1] = {0};
  snprintf(szInfo, DEBUG_INFO_LEN, "%s .net %s %d %d %s; 1 drivers", label,
           name, msb, lsb, argv[0].text);
  node->add_readable_info(szInfo);
#endif
  __vpiScope* scope = vpip_peek_current_scope();
  do_compile_net(node, array, scope, label, name, msb, lsb, array_addr,
                 vpi_type_code, signed_flag, local_flag);

  free(argv[0].text);
  free(argv);
}

bool __compile_net_resolv::resolve(bool msg_flag) {
  vvp_net_t* node = vvp_net_lookup(label());
  if (node == 0) {
    if (msg_flag) cerr << "Unable to resolve label " << label() << endl;
    return false;
  }

  do_compile_net(node, array_, scope_, my_label_, name_, msb_, lsb_,
                 array_addr_, vpi_type_code_, signed_flag_, local_flag_);
  return true;
}

void compile_net(char* label, char* name, int msb, int lsb, int vpi_type_code,
                 bool signed_flag, bool local_flag, unsigned argc,
                 struct symb_s* argv) {
  __compile_net(label, name, 0, 0, msb, lsb, vpi_type_code, signed_flag,
                local_flag, argc, argv);
}

void compile_netw(char* label, char* array_label, unsigned long array_addr,
                  int msb, int lsb, int vpi_type_code, bool signed_flag,
                  unsigned argc, struct symb_s* argv) {
  __compile_net(label, 0, array_label, array_addr, msb, lsb, vpi_type_code,
                signed_flag, false, argc, argv);
}

class __compile_real_net_resolv : public base_net_resolv {
 public:
  explicit __compile_real_net_resolv(char* ref_label, vvp_array_t array,
                                     __vpiScope* scope, char* my_label,
                                     char* name, unsigned array_addr,
                                     bool local_flag)
      : base_net_resolv(ref_label, array, scope, my_label, name, array_addr,
                        local_flag) {}

  ~__compile_real_net_resolv() {}

  bool resolve(bool message_flag);

 private:
};

static void __compile_real_net2(vvp_net_t* node, vvp_array_t array,
                                __vpiScope* scope, char* my_label, char* name,
                                unsigned array_addr, bool local_flag) {
  vvp_wire_base* fil = dynamic_cast<vvp_wire_base*>(node->fil);

  if (fil == 0) {
    fil = new vvp_wire_real;
    node->fil = fil;
  }

  vpiHandle obj = 0;
  if (!local_flag) {
    obj = vpip_make_real_net(scope, name, node);
    compile_vpi_symbol(my_label, obj);
  }
#ifdef CHECK_WITH_VALGRIND
  else
    pool_local_net(node);
#endif

  // REMOVE ME! Giving the net a label is a legacy of the times
  // when the .net was a functor of its own. In the long run, we
  // must fix the code generator to not rely on the label of the
  // .net, then we will remove that label.
  define_functor_symbol(my_label, node);

  if (array)
    array->attach_word(array_addr, obj);
  else if (obj)
    vpip_attach_to_scope(scope, obj);

  free(my_label);
  delete[] name;
}

static void __compile_real(char* label, char* name, char* array_label,
                           unsigned long array_addr, int msb, int lsb,
                           bool local_flag, unsigned argc,
                           struct symb_s* argv) {
  assert(msb == 0 && lsb == 0);
  vvp_array_t array = array_label ? array_find(array_label) : 0;
  assert(array_label ? array != 0 : true);

  free(array_label);

  assert(argc == 1);
  vvp_net_t* node = vvp_net_lookup(argv[0].text);
  if (node == 0) {
    /* No existing net, but the string value may be a
       constant. In that case, we will wind up generating a
       bufz node that can carry the constant value. */
    node = create_constant_node(argv[0].text);
  }
  __vpiScope* scope = vpip_peek_current_scope();
  if (node == 0) {
    __compile_real_net_resolv* res = new __compile_real_net_resolv(
        argv[0].text, array, scope, label, name, array_addr, local_flag);
    resolv_submit(res);
    free(argv);
    return;
  }

  assert(node);
  __compile_real_net2(node, array, scope, label, name, array_addr, local_flag);
  free(argv[0].text);
  free(argv);
}

bool __compile_real_net_resolv::resolve(bool msg_flag) {
  vvp_net_t* node = vvp_net_lookup(label());
  if (node == 0) {
    if (msg_flag) cerr << "Unable to resolve label " << label() << endl;
    return false;
  }

  __compile_real_net2(node, array_, scope_, my_label_, name_, array_addr_,
                      local_flag_);
  return true;
}

void compile_net_real(char* label, char* name, int msb, int lsb,
                      bool local_flag, unsigned argc, struct symb_s* argv) {
  __compile_real(label, name, 0, 0, msb, lsb, local_flag, argc, argv);
}

void compile_netw_real(char* label, char* array_label, unsigned long array_addr,
                       int msb, int lsb, unsigned argc, struct symb_s* argv) {
  __compile_real(label, 0, array_label, array_addr, msb, lsb, false, argc,
                 argv);
}
